﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks.Dataflow;
using F2Enum.Converters;
using F2Enum.Core;
using Fasterflect;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Microsoft.Practices.ServiceLocation;

namespace F2Enum.Library
{
    /// <summary>
    /// Classe de conversion de données dans des fichiers plats vers une liste d'objets
    /// </summary>
    public sealed class F2EnumLibrary
    {
        public static string DateFormatString = string.Empty;

        static F2EnumLibrary()
        {
            BootStrapper.Init();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="filePath"></param>
        /// <param name="mappings"></param>
        /// <param name="action"></param>
        public static void ConvertToObjectsWidthActions<T>(string filePath, IEnumerable<MapperClass<T>> mappings, Action<T> action)
            where T : class,new()
        {
            ConvertToObjectsWidthActions(filePath, mappings, (t, u) => action.Invoke(t));
        }

        /// <summary>
        /// Convert Flat file on object and do an action on it 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="filePath"></param>
        /// <param name="mappings"></param>
        /// <param name="action"></param>
        public static void ConvertToObjectsWidthActions<T>(string filePath, IEnumerable<MapperClass<T>> mappings, Action<T, string> action)
            where T : class,new()
        {
            var dataflowOptions = new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount };

            var list = new List<Tuple<string, MemberExpression, MapperClass<T>>>();
            foreach (var mapping in mappings)
            {
                MemberExpression memberExpression = null;
                if (mapping.Member.Body.NodeType == ExpressionType.Convert)
                {
                    var body = (UnaryExpression)mapping.Member.Body;
                    memberExpression = body.Operand as MemberExpression;
                }
                else if (mapping.Member.Body.NodeType == ExpressionType.MemberAccess)
                {
                    memberExpression = mapping.Member.Body as MemberExpression;
                }
                if (memberExpression != null)
                {
                    var memberStringWithoutRoot = memberExpression.ToString().Split('.').Skip(1).Aggregate((current, next) => string.Concat(current, ".", next));
                    list.Add(new Tuple<string, MemberExpression, MapperClass<T>>(memberStringWithoutRoot, memberExpression, mapping));
                }
            }
            var emptyInstance = CreateInstances<T>(list.ToDictionary(c => c.Item1, c => c.Item2));
            var broadCastBLoc = new BroadcastBlock<string>(l => l, dataflowOptions);
            var bufferedBloc = new BufferBlock<string>(dataflowOptions);
            var trasformationBloc = new TransformBlock<string, T>(
                l =>
                {
#if DEBUG
                    var watch = Stopwatch.StartNew();
#endif
                    var result = new T();
#if DEBUG
                    watch.Stop();
                    Debug.WriteLine(watch.Elapsed);
#endif
                    foreach (var dicoMap in list)
                    {
                        if (l != null)
                        {
                            var valeur = l.Substring(dicoMap.Item3.Offset - 1, dicoMap.Item3.Length).Trim();
                            //if (dicoMap.Key.MemberAction != null)
                            //{
                            //    valeur = dicoMap.Key.MemberAction.Invoke(valeur);
                            //}
                            //PopulateObject(dicoMap.Item1, result, valeur, dicoMap.Item3.DateFormat);
                            //CreateInstances(instance, dicoMap.Value, valeur, dicoMap.Key.DateFormat);
                        }
                    }

                    return result;
                }, dataflowOptions);
            var joinBLoc = new JoinBlock<string, T>();
            var actionBloc = new ActionBlock<Tuple<string, T>>(tuple => action(tuple.Item2, tuple.Item1));
            broadCastBLoc.LinkTo(bufferedBloc);
            broadCastBLoc.LinkTo(trasformationBloc);
            trasformationBloc.LinkTo(joinBLoc.Target2);
            bufferedBloc.LinkTo(joinBLoc.Target1);
            joinBLoc.LinkTo(actionBloc);
            broadCastBLoc.Completion.ContinueWith(t =>
                                                      {
                                                          bufferedBloc.Complete();
                                                          trasformationBloc.Complete();
                                                      });
            Task.Factory.ContinueWhenAll(new[] { bufferedBloc.Completion, trasformationBloc.Completion },
                                         t => joinBLoc.Complete());
            joinBLoc.Completion.ContinueWith(t => actionBloc.Complete());

            Task.Factory.StartNew(
                () =>
                {
                    using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                    {
                        using (var lines = new StreamReader(fs))
                        {
                            string line;
                            while (!string.IsNullOrEmpty((line = lines.ReadLine())))
                            {
                                broadCastBLoc.SendAsync(line);
                            }

                        }
                    }
                }, TaskCreationOptions.LongRunning).ContinueWith(t => broadCastBLoc.Complete());

            actionBloc.Completion.Wait();
        }

        /// <summary>
        /// Methode pour convertir un fichier plats en liste de dictionnaires (Nom de la propriété/ Valeur)
        /// </summary>
        /// <typeparam name="T">class de cast</typeparam>
        /// <param name="filePath">chemin du fichier plat</param>
        /// <param name="mappings">liste d'objet contenant le mmapping entre les membres de la classe de cast et les positions des informations dans le fichier plat</param>
        /// <returns>Liste d'objets</returns>
        public static IEnumerable<T> ConvertToEnumerable<T>(string filePath, IEnumerable<MapperClass<T>> mappings)
            where T : class,new()
        {
            var entitiesList = new BlockingCollection<T>();

            ConvertToObjectsWidthActions(filePath, mappings, entitiesList.Add);
            return entitiesList;
        }

        /// <summary>
        /// Create the Instance from Member Expression
        /// </summary>
        /// <typeparam name="T">
        /// Tresult
        /// </typeparam>
        /// <param name="memberExpressions"> </param>
        private static T CreateInstances<T>(Dictionary<string, MemberExpression> memberExpressions)
            where T : class ,new()
        {
            var obj = typeof(T).CreateInstance() as T;
            foreach (var memberExpression in memberExpressions)
            {
                object parentObject = null;
                var listmembers = memberExpression.Key.Split('.').ToList();
                if (obj != null)
                {
                    var property = obj.GetType().Property(listmembers[0]);
                    if (memberExpression.Value.Expression.NodeType == ExpressionType.Parameter)
                    {
                        parentObject = obj;
                    }
                    else
                    {
                        parentObject = property.GetValue(obj, null) ?? Activator.CreateInstance(property.PropertyType);
                        property.SetValue(obj, parentObject, null);
                    }
                }

                if (listmembers.Count > 1)
                {
                    listmembers.Skip(1).ToList().ForEach(
                        member =>
                        {
                            if (!(memberExpression.Value.Member.Name.Equals(member) || memberExpression.Value.Expression.NodeType == ExpressionType.Parameter))
                            {
                                var childrenproperty = parentObject.GetType().GetProperty(member);
                                var childrenObject = childrenproperty.GetValue(parentObject, null) ?? Activator.CreateInstance(childrenproperty.PropertyType);
                                childrenproperty.SetValue(parentObject, childrenObject, null);
                                parentObject = childrenObject;
                            }
                        });
                }
            }
            return obj;
        }

        private static void PopulateObject(string member, object parentObject, string value, string dateFormat)
        {
            var propertyMember = parentObject.GetType().GetProperty(member);
            var propertyType = propertyMember.PropertyType;
            var converter = ServiceLocator.Current.GetInstance<IConverterFactory>().GetConverter(propertyType);
            var newValue = converter.Convert(value);
            parentObject.TrySetPropertyValue(member, newValue);
        }
    }

    public static class CloneExtensions
    {
        public static T DeepCloneLocal<T>(this T @this)
            where T :class ,new ()
    {
        using (var memStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter(null,
                                                      new StreamingContext(StreamingContextStates.Clone));
            binaryFormatter.Serialize(memStream, @this);
            memStream.Seek(0, SeekOrigin.Begin);
            return binaryFormatter.Deserialize(memStream) as T;
        }
    }
    }
}
